/*
 * ClientContext.java
 *
 * $Id: ClientContext.java,v 1.7 2009-09-17 12:16:12 mario Exp $
 */
package org.ceteca.explica.client;

import java.io.File;
import java.io.InputStream;
import java.net.URL;
import java.rmi.AccessException;
import java.rmi.NotBoundException;
import java.rmi.RemoteException;
import java.rmi.registry.LocateRegistry;
import java.rmi.registry.Registry;
import java.util.ArrayList;
import java.util.EmptyStackException;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Stack;

import org.apache.log4j.Logger;
import org.ceteca.explica.client.gui.ExplicaEntitySelectionView;
import org.ceteca.explica.client.gui.ExplicaEntityView;
import org.ceteca.explica.core.InterfaceErrors;
import org.ceteca.explica.core.InterfaceExplica;
import org.ceteca.explica.core.installer.EntityLauncherFactory;
import org.ceteca.explica.core.installer.EntityOT;
import org.ceteca.explica.core.installer.Installer;
import org.ceteca.explica.core.installer.ModuleOT;
import org.ceteca.explica.core.traceability.InterfaceTraceabilityTree;
import org.ceteca.explica.core.util.LoggerFactory;
import org.ceteca.explica.core.util.OperationResult;
import org.ceteca.explica.core.util.ResultMessage;
import org.ceteca.explica.core.util.ResultMessageCatalog;
import org.ceteca.javaglade.toolkit.swt.SwtTopLevelWindow;
import org.eclipse.swt.widgets.Composite;
import org.xnap.commons.i18n.I18n;
import org.xnap.commons.i18n.I18nFactory;

/**
 * This class lays on the client side of the Explica application and works
 * as an interface for initializing the client generic instances used
 * by the client objects. It also gives access to the generic properties
 * and systems, as for example, logging, internationalization, etc. 
 * <br/>
 * Responsibilities:
 * <li>
 * 	<ul> Initialize client logging system. </ul>
 *  <ul> Initialize client internationalization system. </ul>
 *  <ul> Build the catalog of messages used by the system. </ul>
 *  <ul> Give accessors for all these general objects.  </ul>
 * </li>
 */
public class ClientContext {
	/**
	 * Instance of the internationalization system used by the client objects.
	 */
	private I18n i18n;
	/**
	 * Catalog of result and error messages used by the client objects.
	 */
	private ResultMessageCatalog msgCatalog;
	/**
	 * Instance of the logging system used by the client objects.
	 */
	private Logger logger;
	/**
	 * Flag to show if the client context has been correctly initialized 
	 * and configured. If any error takes places during initialization phase, 
	 * this flag will be unset.
	 */
	private boolean isReady;
	/**
	 * Path to the resources directory where the images, icons, etc. are
	 * stored.
	 */
	private String resourceDir;
	
	/**
	 * Instance of a hidden temporary GUI container for the entity views.
	 * When used the entity views will be linked to a valid container. This
	 * fake container will be used only in terms of construction and initialization
	 * of the entity view components.
	 */
	private SwtTopLevelWindow fakeParent;
	
	/**
	 * Collection of installed entities available for the application.
	 * This entities can be accessed via a magnifying glass (subentity selection)
	 * component. 
	 */
	private HashMap<String, EntityOT> lEntities;
	
	private HashMap<String, EntityLauncherFactory> lEntLaunchersFactories;
	
	/**
	 * Stack of available entity views. Each entity view will be used as a
	 * component of the GUI to show the data related to an entity.
	 */
	private Stack<ExplicaEntityView> lEntViews;
	/**
	 * Number of entity view components built.
	 */
	private int numEntViews;
	
	/**
	 * Stack of available entity selection views. Each entity selection view 
	 * will be used as a component of the GUI to show a selection dialog.
	 */
	private Stack<ExplicaEntitySelectionView> lEntSelViews;
	/**
	 * Number of entity view components built.
	 */
	private int numEntSelViews;
	
	private Stack lUsedViews;
	
	/** Unique instance of the ClientContext class. Singleton pattern */
	private static ClientContext instance;
	
	/** 
	 * Returns the unique instance of the ClientContext class.
	 * Implements the singleton dessign pattern.
	 * @return ClientContext, unique instance of ClientContext.
	 */
	public static final ClientContext getInstance() {
		if (instance == null)
			instance = new ClientContext();
	
		return instance;
	}
	
	/**
	 * Default ClientContext constructor.
	 */
	private ClientContext() {
		this.i18n = null;
		this.msgCatalog = null;
		this.logger = null;
		this.lEntities = new HashMap<String, EntityOT>();
		this.lEntLaunchersFactories = new HashMap<String, EntityLauncherFactory>();
		this.lEntViews = new Stack<ExplicaEntityView>();
		this.numEntViews = 0;
		this.lEntSelViews = new Stack<ExplicaEntitySelectionView>();
		this.numEntSelViews = 0;
		this.lUsedViews = new Stack();
	}

	/**
	 * Inits the client context and builds all the generic objects.
	 */
	public void initClientContext() {
		// Init the logger service
		try {
			System.out.println(this.getClass().getResource(InterfaceExplica.EXPLICA_LOG_CONFIG_FILE).getPath());
			LoggerFactory.initLogger(
					InterfaceExplica.EXPLICA_LOG_DIR,
					this.getClass().getResourceAsStream(InterfaceExplica.EXPLICA_LOG_CONFIG_FILE));
			this.logger = LoggerFactory.getLogger(InterfaceExplica.EXPLICA_LOG_CATEGORY);
		}
		catch (Exception ex) {
			System.err.println("The log system cannot be initialized!");
			ex.printStackTrace();
			this.logger = null;
		}
		
		// Get the internationalization object instance
		try {
			this.i18n = I18nFactory.getI18n(ClientManager.class);
		}
		catch (Exception ex) {
			System.err.println("The internationalization system cannot be initialized!");
			ex.printStackTrace();
			this.i18n = null;
		}
		
		try {
			System.out.println(this.getClass().getResource(InterfaceExplica.EXPLICA_MESSAGES_PROPERTIES_FILE).getPath());
			
			// Build the catalog of result messages available for the module
			this.msgCatalog = new ResultMessageCatalog(
					this.getClass().getResourceAsStream(InterfaceExplica.EXPLICA_MESSAGES_PROPERTIES_FILE), 
					this.i18n, LoggerFactory.getLogger(InterfaceExplica.EXPLICA_LOG_CATEGORY)
			);
			
			// Add the messages defined for the traceability tree viewer to the catalog of result messages.
			try {
				System.out.println(this.getClass().getResource(InterfaceTraceabilityTree.TREEVIEWER_MESSAGES_PROPERTIES_FILE).getPath());
			}
			catch (Exception ex) {
				ex.printStackTrace();
			}
			this.msgCatalog.addResultMessages(this.getClass().getResourceAsStream(InterfaceTraceabilityTree.TREEVIEWER_MESSAGES_PROPERTIES_FILE));

			
			// Print the loaded result messages list.
			System.out.println("-------------- Catalogo de mensajes -------------------");
			this.msgCatalog.printMessages();
			System.out.println("-------------------------------------------------------");
		}
		catch (Exception ex) {
			System.err.println("The catalog of result messages cannot be built!");
			ex.printStackTrace();
			this.msgCatalog = null;
		}
		
		// Init the resources directory
		try {
			// Get the resources configuration file
			// URL url = this.getClass().getResource(InterfaceExplica.INSTALLER_LOCAL_RESOURCES_PROPERTIES_FILE);
			URL url = this.getClass().getResource("/resources/resources");
			File f = null;
			if (url != null) {
				System.out.println("RESOURCES CONFIGURATION FILE = " + url.getPath());
				f = new File(url.getPath());
			}
			else {
				f = new File(InterfaceExplica.INSTALLER_LOCAL_RESOURCES_PROPERTIES_FILE);				
			}

			this.resourceDir = f.getParent() + File.separator + "img";
			System.out.println("RESOURCES DIRECTORY = " + this.resourceDir);
		}
		catch (Exception ex) {
			System.err.println("The resources directory cannot be found or acessed!");
			ex.printStackTrace();
			this.resourceDir = null;
		}
		
		// Build the fake parent the entity views will hung on until they are reassigned
		this.fakeParent = new SwtTopLevelWindow("FAKE_WINDOW", false, 0, 0, null, "Fake window");
		
		this.setReady(this.logger != null && this.i18n != null && this.msgCatalog != null && this.resourceDir != null);
		if (this.isReady())
			System.out.println("Context correctly initialized");
		else
			System.err.println("Context can't be initialized");
	}

	/**
	 * Gets the logger instance used by the client objects for system logging messages.
	 * @return Logger, Explica client logger instance.
	 */
	public Logger getLogger() {
		return this.logger;
	}
	
	/**
	 * Gets the logger instance corresponding to the specified category.
	 * @return Logger, category logger instance.
	 */
	public Logger getLogger(String category) {
		return LoggerFactory.getLogger(category);
	}

	/**
	 * Gets the internationalization object used by the client objects
	 * for translating messages. 
	 * @return I18n, Explica client internationalization instance.
	 */
	public I18n getI18n() {
		return i18n;
	}

	/**
	 * Gets the catalog of result and error messages showed to the user
	 * as the result of the operations made by the client objects. 
	 * @return ResultMessageCatalog, Explica client messages catalog.
	 */
	public ResultMessageCatalog getMsgCatalog() {
		return msgCatalog;
	}

	/**
	 * Adds a list of result messages defined for an application module to
	 * the catalog of result messages of the client application.
	 * @param isResourcesFile InputStream, stream to the properties file
	 * of the module result messages.
	 */
	public void addModuleResultMessages(InputStream isResourcesFile) {
		if (this.msgCatalog != null)
			this.msgCatalog.addResultMessages(isResourcesFile);
	}
	
	/**
	 * Gets the path to the local resources directory. 
	 * @return String, path of the resources directory.
	 */
	public String getResourceDir() {
		return resourceDir;
	}

	/**
	 * Gets the value of the flag isReady.
	 * @return boolean, True if the client context was correctly configured; 
	 * False in case any error took place during the module configuration.
	 */
	public boolean isReady() {
		return this.isReady;
	}

	/**
	 * Sets up the value of the flag isReady.
	 * @param ready boolean, True if the client context was correctly configured; 
	 * False in case any error took place during the module configuration.
	 */
	public void setReady(boolean isReady) {
		this.isReady = isReady;
	}
	
	/**
	 * Gets an entity from the entity catalog. It seeks for
	 * an entity name among the available (installed) entities list.
	 * @param entId String, name of the searched entity.
	 * @return EntityOT, data of the entity.
	 */
	public EntityOT getEntity(String entId) {
		if (this.lEntities != null)
			return this.lEntities.get(entId);
		else
			return null;
	}
	
	/**
	 * Gets the complete catalog of available (installed) entities.
	 * @return HashMap<String, EntityOT>, catalog of available entities.
	 */
	public HashMap<String, EntityOT> getEntities() {
		return this.lEntities;
	}
	
	/**
	 * Adds an entity to the available entities catalog.
	 * @param entData EntityOT, data of the entity.
	 */
	public void addEntity(EntityOT entData) {
		if (entData != null && entData.getKey() != null && entData.getKey().length() > 0) {
			this.lEntities.put(entData.getKey(), entData);
			this.logger.debug(String.format("Added entity %s to the catalog!", 
					entData.getKey()));
		} // if entity is not empty
		else {
			this.logger.error("Error adding a null entity to the catalog!" + entData);
		}
	}

	/**
	 * Adds the module's entities list to the available entities catalog.
	 * @param modData ModuleOT, data of the module.
	 */
	public void addModuleEntities(ModuleOT modData) {
		if (modData != null && modData.getLEntities() != null && modData.getLEntities().size() > 0) {
			this.logger.info(String.format("Adding %d entities of the module %s to the catalog...", 
					modData.getLEntities().size(), modData.getName()));
			
			for (int i=0; i<modData.getLEntities().size(); i++) {
				this.addEntity(modData.getLEntities().get(i));
			} // for each entity of the module
		} // if there are any entity defined for the module
		else {
			this.logger.error("Error adding a null module entity list to the catalog!" + modData);
		}
	}
	
	/**
	 * Translates a result message contents and localize it into the
	 * client user locale.
	 * @param resMsg ResultMessage, message to translate.
	 * @return ResultMessage, translated message.
	 */
	public OperationResult localizeOperationResult(OperationResult res) {
		OperationResult result = new OperationResult();
		if (this.msgCatalog != null && this.i18n != null) {
			if (res != null && res.getResultMessagesList()!= null && res.getResultMessagesList().size() > 0) {
				for (int i=0; i<res.getResultMessagesList().size(); i++) {
					ResultMessage resMsg = res.getResultMessagesList().get(i);
					resMsg = this.msgCatalog.translateMessage(resMsg);
					result.addResultMessage(OperationResult.RESULT_UNDEFINED, resMsg);
				} // for each result message
			} // if there are any result messages
			else {
				result = res;
			}
		}
		else {
			this.logger.error("Trying to translate a message but the client context is not correctly initialized!");
			result = res;
		}
		
		return result;
	}
	
	public ArrayList getAreasCatalog() {
		// Get the catalog of available areas
		ArrayList lAreas = null;
		try {
			// Locate the remote object that will serve the request. The
			// name of the searched remote object must be the same used
			// in the moment of the remote object registry.
			Registry registry = LocateRegistry
					.getRegistry(InterfaceExplica.RMI_REGISTRY_PORT);
			ClientContext.getInstance().getLogger().debug("Registry located");
			Installer ins = (Installer) registry
					.lookup(InterfaceExplica.JNDI_INSTALLER_EXPORT_NAME);
			ClientContext.getInstance().getLogger().debug("Installer located");

			// Check the version of all the installed modules
			try {
				lAreas = (ArrayList) ins.getAreasList();

				StringBuffer buf = new StringBuffer();
				buf.append("Areas list gotten! ");
				buf.append(lAreas.size());
				buf.append(" areas have been found!");

				for (int i = 0; i < lAreas.size(); i++) {
					buf.append("\n\t\tAREA ");
					buf.append(i);
					buf.append(": ");
					buf.append(lAreas.get(i));
				} // for

				ClientContext.getInstance().getLogger().info(buf.toString());
			} // try (getAreas)
			catch (Exception rEx) {
				ClientContext.getInstance().getLogger().error(
						InterfaceExplica.EXPLICA_LOG_ERROR_GETTING_AREAS, rEx);
			} // catch
		} // try (get remote object)
		catch (AccessException aEx) {
			ArrayList lParams = new ArrayList();
			lParams.add(InterfaceExplica.JNDI_INSTALLER_EXPORT_NAME);
			ResultMessage msg = ClientContext.getInstance().getMsgCatalog()
					.newResultMessage(InterfaceErrors.ERR_RMI_NO_ACCESS, aEx,
							lParams);

			ClientContext.getInstance().getLogger()
					.error(msg.getMessage(), aEx);
			lAreas = null;
		} // catch (AccessException)
		catch (NotBoundException nEx) {
			ArrayList lParams = new ArrayList();
			lParams.add(InterfaceExplica.JNDI_INSTALLER_EXPORT_NAME);
			ResultMessage msg = ClientContext.getInstance().getMsgCatalog()
					.newResultMessage(InterfaceErrors.ERR_RMI_NOT_BOUND, nEx,
							lParams);

			ClientContext.getInstance().getLogger()
					.error(msg.getMessage(), nEx);
			lAreas = null;
		} // catch (NotBoundException)
		catch (RemoteException rEx) {
			ArrayList lParams = new ArrayList();
			lParams.add(InterfaceExplica.JNDI_INSTALLER_EXPORT_NAME);
			ResultMessage msg = ClientContext.getInstance().getMsgCatalog()
					.newResultMessage(InterfaceErrors.ERR_RMI_METHOD_NOT_FOUND,
							rEx, lParams);

			ClientContext.getInstance().getLogger()
					.error(msg.getMessage(), rEx);
			lAreas = null;
		} // catch (RemoteException)
		
		return lAreas;
	}
	
	/**
	 * Gets an entity launcher factory from the catalog. It seeks for
	 * an entity id among the available entity launcher factories' catalog.
	 * @param entId String, key of the searched entity.
	 * @return EntityLauncherFactory, factory for building entity launchers.
	 */
	public EntityLauncherFactory getEntityLauncherFactory(String entId) {
		if (this.lEntLaunchersFactories != null)
			return this.lEntLaunchersFactories.get(entId);
		else
			return null;
	}
	
	/**
	 * Gets the complete catalog of available entity launcher factories.
	 * @return HashMap<String, EntityLauncherFactory>, catalog of available 
	 * entity launcher factories.
	 */
	public HashMap<String, EntityLauncherFactory> getEntityLauncherFactories() {
		return this.lEntLaunchersFactories;
	}
	
	/**
	 * Creates a new entity launcher factory and insert it into the catalog.
	 * @param entData EntityOT, data of the entity whose launcher factory will
	 * be created.
	 * @param logger Logger, logger instance used for the entities build by
	 * the factory.
	 */
	public void initEntityLauncherFactory(EntityOT entData, Logger logger) {
		if (entData != null && entData.getKey() != null && entData.getKey().length() > 0) {
			EntityLauncherFactory factory = new EntityLauncherFactory(entData, logger);
			this.lEntLaunchersFactories.put(entData.getKey(), factory);
		} // if entity is not empty
		else {
			this.logger.error("Error creating an entity launcher factory for a null entity! " + entData);
		}
	}
	
	public void printEntityLaunchers() {
		this.printEntityLaunchers(this.logger);
	}
	
	public void printEntityLaunchers(Logger logger) {
		StringBuffer buf = new StringBuffer();
		if (this.lEntLaunchersFactories != null) {
			Iterator it = this.lEntLaunchersFactories.keySet().iterator();
			while (it != null && it.hasNext()) {
				String entKey = (String)it.next();
				buf.append(String.format("\n<====== ENTITY %s ======>%s\n</===== ENTITY %s =====\\>",
						entKey, this.lEntLaunchersFactories.get(entKey).toString(), entKey));
			}
		}
		
		logger.info(buf.toString());
	}
	
	/**
	 * Builds and initializes the stack of available entity view components.
	 */
	public void buildEntityViews() {
		for (int i=0; i<InterfaceExplica.EXPLICA_INIT_ENTITY_VIEW_SIZE; i++) {
			ExplicaEntityView entView = new ExplicaEntityView(InterfaceExplica.EXPLICA_ENTITY_GLADE_NAME + i);
			entView.buildEntityView();
			this.lEntViews.push(entView);
		}
		
		this.numEntViews = InterfaceExplica.EXPLICA_INIT_ENTITY_VIEW_SIZE;
	}
	
	/**
	 * Returns the fake hidden container for building the entity view 
	 * components before embedding them in a valid GUI container. 
	 * @return Composite, fake hidden container.
	 */
	public Composite getFakeParent() {
		return this.fakeParent.getShell();
	}
	
	/**
	 * Returns the fake hidden window for building the entity 
	 * selection view components before linking them to a valid
	 * window. 
	 * @return SwtTopLevelWindow, fake hidden window.
	 */
	public SwtTopLevelWindow getFakeWindow() {
		return this.fakeParent;
	}
	
	/**
	 * Returns an available instance of an entity view component from the stack.
	 * @return ExplicaEntityView, available instance of entity view component.
	 */
	public ExplicaEntityView getAvailableEntityView() {
		ExplicaEntityView entView = null;
		try {
			entView = this.lEntViews.pop();
		}
		catch (EmptyStackException eEx) {
			// Build a new instance of Entity View component
			entView = new ExplicaEntityView(InterfaceExplica.EXPLICA_ENTITY_GLADE_NAME + this.numEntViews++);
			entView.buildEntityView();
		}
		
		return entView;
	}
	
	public void releaseEntityView(ExplicaEntityView entView) {
		if (this.lEntViews != null && !this.lEntViews.contains(entView)) {
			entView.clearView();
			this.lEntViews.push(entView);
		}
	}
	
	/**
	 * Builds and initializes the stack of available entity view components.
	 */
	public void buildEntitySelectionViews() {
/*		for (int i=0; i<InterfaceExplica.EXPLICA_INIT_ENTITY_VIEW_SIZE; i++) {
			ExplicaEntitySelectionView entSelView = new ExplicaEntitySelectionView(InterfaceExplica.EXPLICA_SUBENTITY_SELECTION_GLADE_NAME + i);
			entSelView.buildSelectionDialog();
			this.lEntSelViews.push(entSelView);
		}
		
		this.numEntViews = InterfaceExplica.EXPLICA_INIT_ENTITY_VIEW_SIZE;
*/
	}
	
	/**
	 * Returns an available instance of an entity view component from the stack.
	 * @return ExplicaEntityView, available instance of entity view component.
	 */
	public ExplicaEntitySelectionView getAvailableEntitySelectionView() {
/*		ExplicaEntitySelectionView entSelView = null;
		try {
			entSelView = this.lEntSelViews.pop();
		}
		catch (EmptyStackException eEx) {
			// Build a new instance of Entity View component
			entSelView = new ExplicaEntitySelectionView(InterfaceExplica.EXPLICA_ENTITY_GLADE_NAME + this.numEntSelViews++);
			entSelView.buildSelectionDialog();
		}
		
		return entSelView;
*/
		return null;
	}
	
	public void releaseEntitySelectionView(ExplicaEntitySelectionView entSelView) {
/*		if (this.lEntSelViews != null && !this.lEntSelViews.contains(entSelView)) {
			entSelView.clearView();
			this.lEntSelViews.push(entSelView);
		}
*/
	}
		
	/**
	 * Returns tag Id assigned to CVS source file.
	 */
	public static String getRevision() {
		return "$Id: ClientContext.java,v 1.7 2009-09-17 12:16:12 mario Exp $";
	}
}
